package com.eedi.framework.permission.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.eedi.framework.permission.controller.sys.vo.role.SysRolePageReq;
import com.eedi.framework.permission.controller.sys.vo.role.SysRoleSaveReq;
import com.eedi.framework.permission.controller.sys.vo.role.SysRoleSimpleResp;
import com.eedi.framework.permission.controller.sys.vo.role.SysRoleUpReq;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Suppliers;
import com.google.common.collect.Sets;
import com.eedi.framework.common.enums.CommonStatusEnum;
import com.eedi.framework.common.enums.YesOrNoEnum;
import com.eedi.framework.common.pojo.PageResult;
import com.eedi.framework.common.util.collection.CollectionUtils;
import com.eedi.framework.datapermission.core.annotation.DataPermission;
import com.eedi.framework.permission.controller.sys.vo.menu.SysMenuListReq;
import com.eedi.framework.permission.controller.sys.vo.SysDeptDataPermissionRespDTO;
import com.eedi.framework.permission.controller.sys.vo.menu.SysMenuSaveReq;
import com.eedi.framework.permission.controller.sys.vo.role.*;
import com.eedi.framework.permission.dal.SysPermissionCacheKeyConstants;
import com.eedi.framework.permission.dal.dataobject.SysRoleDO;
import com.eedi.framework.permission.dal.dataobject.SysRoleMenuDO;
import com.eedi.framework.permission.dal.mapper.SysRoleMenuMapper;
import com.eedi.framework.permission.dal.mapper.SysUserRoleMapper;
import com.eedi.framework.permission.enums.SysDataScopeEnum;
import com.eedi.framework.permission.enums.SysRoleCodeEnum;
import com.eedi.framework.permission.facade.SysPermissionFacade;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.eedi.framework.permission.dal.dataobject.SysMenuDO;
import com.eedi.framework.permission.dal.dataobject.SysUserRoleDO;
import javax.annotation.Resource;
import java.util.*;
import java.util.function.Supplier;

import static com.eedi.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.eedi.framework.common.util.collection.CollectionUtils.convertSet;
import static com.eedi.framework.common.util.json.JsonUtils.toJsonString;
import static com.eedi.framework.permission.enums.SysPermissionErrorCodeConstants.SYS_MENU_NOT_EXISTS;

/**
 * 权限 Service 实现类
 *
 * @author 永聚长青源码
 */
@Service
@Slf4j
public class SysPermissionServiceImpl implements SysPermissionService {

    @Resource
    private SysRoleMenuMapper sysRoleMenuMapper;
    @Resource
    private SysUserRoleMapper sysUserRoleMapper;

    @Resource
    private SysRoleService sysRoleService;
    @Resource
    private SysMenuService sysMenuService;
    @Resource
    private SysPermissionFacade sysPermissionFacade;

    @Override
    public boolean hasAnyPermissions(String userId, String... permissions) {
        // 如果为空，说明已经有权限
        if (ArrayUtil.isEmpty(permissions)) {
            return true;
        }

        // 获得当前登录的角色。如果为空，说明没有权限
        List<SysRoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);
        if (CollUtil.isEmpty(roles)) {
            return false;
        }

        // 情况一：遍历判断每个权限，如果有一满足，说明有权限
        for (String permission : permissions) {
            if (hasAnyPermission(roles, permission)) {
                return true;
            }
        }

        // 情况二：如果是超管，也说明有权限
        return sysRoleService.hasAnySuperAdmin(convertSet(roles, SysRoleDO::getSysRoleId));
    }

    /**
     * 判断指定角色，是否拥有该 permission 权限
     *
     * @param roles 指定角色数组
     * @param permission 权限标识
     * @return 是否拥有
     */
    private boolean hasAnyPermission(List<SysRoleDO> roles, String permission) {
        List<String> menuIds = sysMenuService.getMenuIdListByPermissionFromCache(permission);
        // 采用严格模式，如果权限找不到对应的 Menu 的话，也认为没有权限
        if (CollUtil.isEmpty(menuIds)) {
            return false;
        }

        // 判断是否有权限
        Set<String> roleIds = convertSet(roles, SysRoleDO::getSysRoleId);
        for (String menuId : menuIds) {
            // 获得拥有该菜单的角色编号集合
            Set<String> menuRoleIds = getSelf().getMenuRoleIdListByMenuIdFromCache(menuId);
            // 如果有交集，说明有权限
            if (CollUtil.containsAny(menuRoleIds, roleIds)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean hasAnyRoles(String userId, String... roles) {
        // 如果为空，说明已经有权限
        if (ArrayUtil.isEmpty(roles)) {
            return true;
        }

        // 获得当前登录的角色。如果为空，说明没有权限
        List<SysRoleDO> roleList = getEnableUserRoleListByUserIdFromCache(userId);
        if (CollUtil.isEmpty(roleList)) {
            return false;
        }

        // 判断是否有角色
        Set<String> userRoles = convertSet(roleList, SysRoleDO::getSysRoleCode);
        return CollUtil.containsAny(userRoles, Sets.newHashSet(roles));
    }

    // ========== 角色-菜单的相关方法  ==========

    @Override
    @DSTransactional // 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换
    @CacheEvict(value = SysPermissionCacheKeyConstants.MENU_ROLE_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，主要一次更新涉及到的 menuIds 较多，反倒批量会更快
    public void assignRoleMenu(String roleId, Set<String> menuIds) {
        // 检查新增菜单是否存在
       sysMenuService.validateMenuIds(menuIds);
        // 获得角色拥有菜单编号
        Set<String> dbMenuIds = convertSet(sysRoleMenuMapper.selectListByRoleId(roleId), SysRoleMenuDO::getSysMenuId);
        // 计算新增和删除的菜单编号
        Set<String> menuIdList = CollUtil.emptyIfNull(menuIds);
        Collection<String> createMenuIds = CollUtil.subtract(menuIdList, dbMenuIds);
        Collection<String> deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIdList);
        // 执行新增和删除。对于已经授权的菜单，不用做任何处理
        if (CollUtil.isNotEmpty(createMenuIds)) {
            sysRoleMenuMapper.insertBatch(CollectionUtils.convertList(createMenuIds, menuId -> {
                SysRoleMenuDO entity = new SysRoleMenuDO();
                entity.setSysRoleId(roleId);
                entity.setSysMenuId(menuId);
                return entity;
            }));
        }
        if (CollUtil.isNotEmpty(deleteMenuIds)) {
            sysRoleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds);
        }
    }

    @Override
    public void removeRoleMenu(String roleId, Set<String> menuIds) {
        // 检查新增菜单是否存在
        sysMenuService.validateMenuIds(menuIds);
        // 获得角色拥有菜单编号
        Set<String> dbMenuIds = convertSet(sysRoleMenuMapper.selectListByRoleId(roleId), SysRoleMenuDO::getSysMenuId);
        //检查是否有该菜单
        for (String menuId : dbMenuIds){
           if (!dbMenuIds.contains(menuId)){
               throw exception(SYS_MENU_NOT_EXISTS);
           }
        }
        sysRoleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, menuIds);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Caching(evict = {
            @CacheEvict(value = SysPermissionCacheKeyConstants.MENU_ROLE_ID_LIST,
                    allEntries = true), // allEntries 清空所有缓存，此处无法方便获得 roleId 对应的 menu 缓存们
            @CacheEvict(value = SysPermissionCacheKeyConstants.USER_ROLE_ID_LIST,
                    allEntries = true) // allEntries 清空所有缓存，此处无法方便获得 roleId 对应的 user 缓存们
    })
    public void processRoleDeleted(String roleId) {
        // 标记删除 UserRole
        sysUserRoleMapper.deleteListByRoleId(roleId);
        // 标记删除 RoleMenu
        sysRoleMenuMapper.deleteListByRoleId(roleId);
    }

    @Override
    @CacheEvict(value = SysPermissionCacheKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
    public void processMenuDeleted(String menuId) {
        sysRoleMenuMapper.deleteListByMenuId(menuId);
    }

    @Override
    public Set<String> getRoleMenuListByRoleId(Collection<String> roleIds) {
        if (CollUtil.isEmpty(roleIds)) {
            return Collections.emptySet();
        }

        // 如果是管理员的情况下，获取全部菜单编号
        if (sysRoleService.hasAnySuperAdmin(roleIds)) {
            return convertSet(sysMenuService.getMenuList(), SysMenuDO::getSysMenuId);
        }
        // 如果是非管理员的情况下，获得拥有的菜单编号
        return convertSet(sysRoleMenuMapper.selectListByRoleId(roleIds), SysRoleMenuDO::getSysMenuId);
    }

    @Override
    @Cacheable(value = SysPermissionCacheKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
    public Set<String> getMenuRoleIdListByMenuIdFromCache(String menuId) {
        return convertSet(sysRoleMenuMapper.selectListByMenuId(menuId), SysRoleMenuDO::getSysRoleId);
    }

    // ========== 用户-角色的相关方法  ==========

    @Override
    @DSTransactional // 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换
    @CacheEvict(value = SysPermissionCacheKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
    public void assignUserRole(String userId, Set<String> roleIds) {
        // 获得角色拥有角色编号
        Set<String> dbRoleIds = convertSet(sysUserRoleMapper.selectListByUserId(userId),
                SysUserRoleDO::getSysRoleId);
        // 计算新增和删除的角色编号
        Set<String> roleIdList = CollUtil.emptyIfNull(roleIds);
        Collection<String> createRoleIds = CollUtil.subtract(roleIdList, dbRoleIds);
        Collection<String> deleteMenuIds = CollUtil.subtract(dbRoleIds, roleIdList);
        // 执行新增和删除。对于已经授权的角色，不用做任何处理
        if (!CollectionUtil.isEmpty(createRoleIds)) {
            sysUserRoleMapper.insertBatch(CollectionUtils.convertList(createRoleIds, roleId -> {
                SysUserRoleDO entity = new SysUserRoleDO();
                entity.setSysUserId(userId);
                entity.setSysRoleId(roleId);
                return entity;
            }));
        }
        if (!CollectionUtil.isEmpty(deleteMenuIds)) {
            sysUserRoleMapper.deleteListByUserIdAndRoleIdIds(userId, deleteMenuIds);
        }
    }

    @Override
    @CacheEvict(value = SysPermissionCacheKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
    public void processUserDeleted(String userId) {
        sysUserRoleMapper.deleteListByUserId(userId);
    }

    @Override
    public Set<String> getUserRoleIdListByUserId(String userId) {
        return convertSet(sysUserRoleMapper.selectListByUserId(userId), SysUserRoleDO::getSysRoleId);
    }

    @Override
    @Cacheable(value = SysPermissionCacheKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
    public Set<String> getUserRoleIdListByUserIdFromCache(String userId) {
        return getUserRoleIdListByUserId(userId);
    }

    @Override
    public Set<String> getUserRoleIdListByRoleId(Collection<String> roleIds) {
        return convertSet(sysUserRoleMapper.selectListByRoleIds(roleIds), SysUserRoleDO::getSysUserId);
    }

    /**
     * 获得用户拥有的角色，并且这些角色是开启状态的
     *
     * @param userId 用户编号
     * @return 用户拥有的角色
     */
    @VisibleForTesting
    List<SysRoleDO> getEnableUserRoleListByUserIdFromCache(String userId) {
        // 获得用户拥有的角色编号
        Set<String> roleIds = getSelf().getUserRoleIdListByUserIdFromCache(userId);
        // 获得角色数组，并移除被禁用的
        List<SysRoleDO> roles = sysRoleService.getRoleListFromCache(roleIds);
        roles.removeIf(role -> !CommonStatusEnum.ENABLE.equals(role.getSysRoleStatus()));
        return roles;
    }

    // ========== 用户-部门的相关方法  ==========

    @Override
    public void assignRoleDataScope(String roleId, SysDataScopeEnum dataScope, Set<String> dataScopeDeptIds) {
        sysRoleService.updateRoleDataScope(roleId, dataScope, dataScopeDeptIds);
    }

    @Override
    @DataPermission(enable = false) // 关闭数据权限，不然就会出现递归获取数据权限的问题
    public SysDeptDataPermissionRespDTO getDeptDataPermission(String userId) {
        // 获得用户的角色
        List<SysRoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);

        // 如果角色为空，则只能查看自己
        SysDeptDataPermissionRespDTO result = new SysDeptDataPermissionRespDTO();
        if (CollUtil.isEmpty(roles)) {
            result.setSelf(YesOrNoEnum.YES);
            return result;
        }

        // 获得用户的部门编号的缓存，通过 Guava 的 Suppliers 惰性求值，即有且仅有第一次发起 DB 的查询
        Supplier<String> userDeptId = Suppliers.memoize(() -> sysPermissionFacade.getDeptIdByUserId(userId));
        // 遍历每个角色，计算
        for (SysRoleDO role : roles) {
            // 为空时，跳过
            if (role.getSysRoleDataScope() == null) {
                continue;
            }
            // 情况一，ALL
            if (Objects.equals(role.getSysRoleDataScope(), SysDataScopeEnum.ALL)) {
                result.setAll(YesOrNoEnum.YES);
                continue;
            }
            // 情况二，DEPT_CUSTOM
            if (Objects.equals(role.getSysRoleDataScope(), SysDataScopeEnum.DEPT_CUSTOM)) {
                CollUtil.addAll(result.getDeptIds(), role.getSysRoleDataScopeDeptIds());
                // 自定义可见部门时，保证可以看到自己所在的部门。否则，一些场景下可能会有问题。
                // 例如说，登录时，基于 t_user 的 username 查询会可能被 dept_id 过滤掉
                CollUtil.addAll(result.getDeptIds(), userDeptId.get());
                continue;
            }
            // 情况三，DEPT_ONLY
            if (Objects.equals(role.getSysRoleDataScope(), SysDataScopeEnum.DEPT_ONLY)) {
                CollectionUtils.addIfNotNull(result.getDeptIds(), userDeptId.get());
                continue;
            }
            // 情况四，DEPT_DEPT_AND_CHILD
            if (Objects.equals(role.getSysRoleDataScope(), SysDataScopeEnum.DEPT_AND_CHILD)) {
                CollUtil.addAll(result.getDeptIds(), sysPermissionFacade.getChildDeptIdListFromCache(userDeptId.get()));
                // 添加本身部门编号
                CollUtil.addAll(result.getDeptIds(), userDeptId.get());
                continue;
            }
            // 情况五，SELF
            if (Objects.equals(role.getSysRoleDataScope(), SysDataScopeEnum.SELF)) {
                result.setSelf(YesOrNoEnum.YES);
                continue;
            }
            // 未知情况，error log 即可
            log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", userId, toJsonString(result));
        }
        return result;
    }

    @Override
    public String createRole(SysRoleSaveReq createReqVO) {
        return sysRoleService.createRole(createReqVO);
    }

    @Override
    public void updateRole(SysRoleUpReq updateReqVO) {
         sysRoleService.updateRole(updateReqVO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteRole(String roleId) {
        sysRoleService.deleteRole(roleId);
        // 删除相关数据
        processRoleDeleted(roleId);
    }

    @Override
    public void updateRoleStatus(String roleId, CommonStatusEnum status) {
        sysRoleService.updateRoleStatus(roleId,status);
    }

    @Override
    public void updateRoleDataScope(String roleId, SysDataScopeEnum dataScope, Set<String> dataScopeDeptIds) {
        sysRoleService.updateRoleDataScope(roleId,dataScope,dataScopeDeptIds);
    }

    @Override
    public SysRoleDO getRole(String roleId) {
        return sysRoleService.getRole(roleId);
    }

    @Override
    public SysRoleDO getRoleFromCache(String roleId) {
        return sysRoleService.getRoleFromCache(roleId);
    }

    @Override
    public List<SysRoleDO> getRoleList(Collection<String> roleIds) {
        return sysRoleService.getRoleList(roleIds);
    }

    @Override
    public List<SysRoleDO> getRoleListFromCache(Collection<String> roleIds) {
        return sysRoleService.getRoleListFromCache(roleIds);
    }

    @Override
    public List<SysRoleDO> getRoleListByStatus(Collection<CommonStatusEnum> statuses) {
        return sysRoleService.getRoleListByStatus(statuses);
    }

    @Override
    public List<SysRoleDO> getRoleList() {
        return sysRoleService.getRoleList();
    }

    @Override
    public PageResult<SysRoleDO> getRolePage(SysRolePageReq reqVO) {
        return sysRoleService.getRolePage(reqVO);
    }

    @Override
    public boolean hasAnySuperAdmin(Collection<String> roleIds) {
        return sysRoleService.hasAnySuperAdmin(roleIds);
    }

    @Override
    public void validateRoleList(Collection<String> roleIds) {
        sysRoleService.validateRoleList(roleIds);
    }

    @Override
    public String createMenu(SysMenuSaveReq createReqVO) {
        return sysMenuService.createMenu(createReqVO);
    }

    @Override
    public void updateMenu(SysMenuSaveReq updateReqVO) {
        sysMenuService.updateMenu(updateReqVO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteMenu(String menuId) {
        sysMenuService.deleteMenu(menuId);
        processMenuDeleted(menuId);
    }

    @Override
    public List<SysMenuDO> getMenuList() {
        return sysMenuService.getMenuList();
    }

    @Override
    public List<SysMenuDO> getMenuListByTenant(SysMenuListReq reqVO) {
        return sysMenuService.getMenuListByTenant(reqVO);
    }

    @Override
    public List<SysMenuDO> getMenuList(SysMenuListReq reqVO) {
        return sysMenuService.getMenuList(reqVO);
    }

    @Override
    public List<String> getMenuIdListByPermissionFromCache(String permission) {
        return sysMenuService.getMenuIdListByPermissionFromCache(permission);
    }

    @Override
    public SysMenuDO getMenu(String menuId) {
        return sysMenuService.getMenu(menuId);
    }

    @Override
    public List<SysMenuDO> getMenuList(Collection<String> menuIds) {
        return sysMenuService.getMenuList(menuIds);
    }

    @Override
    public Long countAdmin() {
        List<SysRoleSimpleResp> roleByCode = sysRoleService.getRoleByCode(SysRoleCodeEnum.SYS_ADMIN.getCode());
        List<String> sysRoleIds = roleByCode.stream().map(SysRoleSimpleResp::getSysRoleId).toList();
        return sysUserRoleMapper.countByRoleIds(sysRoleIds);
    }

    @Override
    public List<SysRoleSimpleResp> getBySysUserId(String sysUserId) {
        if (StrUtil.isBlank(sysUserId)){
            return List.of();
        }
        List<String> roleIds = sysUserRoleMapper.getBySysUserId(sysUserId);
       return sysRoleService.getRoleSimpleList(roleIds);
    }

    /**
     * 获得自身的代理对象，解决 AOP 生效问题
     *
     * @return 自己
     */
    private SysPermissionServiceImpl getSelf() {
        return SpringUtil.getBean(getClass());
    }

}
